{/* Copyright 2020 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs';
export default Layout;

import docs from 'docs:@react-spectrum/menu';
import menuUtil from 'docs:@react-aria/test-utils/src/menu.ts';
import {HeaderInfo, PropTable, PageDescription, ClassAPI, VersionBadge} from '@react-spectrum/docs';
import packageData from '@react-spectrum/menu/package.json';
import {Keyboard} from '@react-spectrum/text';

```jsx import
import {ActionButton} from '@react-spectrum/button'
import {Item, Menu, MenuTrigger} from '@react-spectrum/menu';
import {Flex} from '@react-spectrum/layout';
```

---
category: Collections
keywords: [menu, dropdown, action menu]
---

# MenuTrigger

<PageDescription>{docs.exports.MenuTrigger.description}</PageDescription>

<HeaderInfo
  packageData={packageData}
  componentNames={['MenuTrigger', 'Menu']}
  sourceData={[
    {type: 'Spectrum', url: 'https://spectrum.adobe.com/page/popover/'}
  ]}
  since="3.0.0" />

## Example

```tsx example
<MenuTrigger>
  <ActionButton>
    Edit
  </ActionButton>
  <Menu>
    <Item>Cut</Item>
    <Item>Copy</Item>
    <Item>Paste</Item>
  </Menu>
</MenuTrigger>
```

## Content

The MenuTrigger accepts exactly two children: the element which triggers the opening of the Menu and the Menu itself. The trigger element must be the first child passed into the MenuTrigger and should support press events.

## Events

MenuTrigger accepts an `onOpenChange` handler which is triggered whenever the Menu is opened or closed.

```tsx example
function Example() {
  let [isOpen, setIsOpen] = React.useState(false);

  return (
    <Flex gap="size-100" alignItems="center">
      <MenuTrigger onOpenChange={setIsOpen}>
        <ActionButton>
            Edit
        </ActionButton>
        <Menu>
          <Item key="cut">Cut</Item>
          <Item key="copy">Copy</Item>
          <Item key="paste">Paste</Item>
        </Menu>
      </MenuTrigger>
      <div>Currently open: {isOpen.toString()}</div>
    </Flex>
  );
}
```

## Long press
By default, a MenuTrigger's Menu is opened by pressing the trigger element or activating it via the <Keyboard>Space</Keyboard> or <Keyboard>Enter</Keyboard> keys. However, there may be cases in which your trigger element
should perform a separate default action on press such as selection, and should only display the Menu when long pressed. This behavior can be changed by providing `"longPress"` to the `trigger` prop. With this prop, the Menu will only be opened upon
pressing and holding the trigger element or by using the <Keyboard>Option</Keyboard> (<Keyboard>Alt</Keyboard> on Windows) + <Keyboard>Down Arrow</Keyboard>/<Keyboard>Up Arrow</Keyboard> keys while focusing the trigger element.

The example below illustrates how one would setup a MenuTrigger to have long press behavior.

```tsx example
import CloneStamp from '@spectrum-icons/workflow/CloneStamp';
import Crop from '@spectrum-icons/workflow/Crop';
import CropRotate from '@spectrum-icons/workflow/CropRotate';
import Slice from '@spectrum-icons/workflow/Slice';
import {Text} from '@react-spectrum/text';

<MenuTrigger trigger="longPress">
  <ActionButton
    aria-label="Crop tool"
    onPress={() => alert('Cropping!')}>
    <Crop />
  </ActionButton>
  <Menu>
    <Item textValue="Crop Rotate">
      <CropRotate />
      <Text>Crop Rotate</Text>
    </Item>
    <Item textValue="Slice">
      <Slice />
      <Text>Slice</Text>
    </Item>
    <Item textValue="Clone stamp">
      <CloneStamp />
      <Text>Clone Stamp</Text>
    </Item>
  </Menu>
</MenuTrigger>
```

## Props

<PropTable component={docs.exports.MenuTrigger} links={docs.links} />

## Visual options

### Align and direction

[View guidelines](https://spectrum.adobe.com/page/popover/#Placement)

The `align` prop aligns the Menu relative to the trigger and the `direction` prop controls the direction the Menu will render.

```tsx example
<Flex gap="size-100">
  <MenuTrigger align="start">
    <ActionButton>Edit</ActionButton>
    <Menu>
      <Item key="cut">Cut</Item>
      <Item key="copy">Copy</Item>
      <Item key="paste">Paste</Item>
    </Menu>
  </MenuTrigger>
  <MenuTrigger align="end" direction="top" shouldFlip={false}>
    <ActionButton>View</ActionButton>
    <Menu>
      <Item key="side">Side bar</Item>
      <Item key="options">Page options</Item>
      <Item key="edit">Edit Panel</Item>
    </Menu>
  </MenuTrigger>
  <MenuTrigger direction="start" align="start">
    <ActionButton>Edit</ActionButton>
    <Menu>
      <Item key="cut">Cut</Item>
      <Item key="copy">Copy</Item>
      <Item key="paste">Paste</Item>
    </Menu>
  </MenuTrigger>
  <MenuTrigger direction="end" align="end">
    <ActionButton>View</ActionButton>
    <Menu>
      <Item key="side">Side bar</Item>
      <Item key="options">Page options</Item>
      <Item key="edit">Edit Panel</Item>
    </Menu>
  </MenuTrigger>
</Flex>
```


### Close on selection

By default, the Menu closes when an item is selected. To change this, set the `closeOnSelect` prop to `false`. This might be useful when multiple selection is used. See [menu selection](./Menu.html#selection) for more information.

```tsx example
<MenuTrigger closeOnSelect={false}>
  <ActionButton>
    View
  </ActionButton>
  <Menu selectionMode="multiple">
    <Item key="side">Side bar</Item>
    <Item key="options">Page options</Item>
    <Item key="edit">Edit Panel</Item>
  </Menu>
</MenuTrigger>
```

### Flipping
By default, the Menu flips direction automatically upon opening when space is limited. To change this, set the `shouldFlip` prop to `false`. Try scrolling the viewport close to the edge of the trigger in the example to see this in action.

```tsx example
<Flex gap="size-100">
  <MenuTrigger shouldFlip>
    <ActionButton>
      View
    </ActionButton>
    <Menu>
      <Item key="side">Side bar</Item>
      <Item key="options">Page options</Item>
      <Item key="edit">Edit Panel</Item>
    </Menu>
  </MenuTrigger>
  <MenuTrigger shouldFlip={false}>
    <ActionButton>
      Edit
    </ActionButton>
    <Menu>
      <Item key="cut">Cut</Item>
      <Item key="copy">Copy</Item>
      <Item key="paste">Paste</Item>
    </Menu>
  </MenuTrigger>
</Flex>
```

### Open

The `isOpen` and `defaultOpen` props on the MenuTrigger control whether the Menu is open by default.
They apply controlled and uncontrolled behavior on the Menu respectively.

```tsx example
function Example() {
  let [open, setOpen] = React.useState(false);

  return (
    <MenuTrigger
      isOpen={open}
      onOpenChange={setOpen}>
      <ActionButton>
        View
      </ActionButton>
      <Menu selectionMode="multiple">
        <Item key="side">Side bar</Item>
        <Item key="options">Page options</Item>
        <Item key="edit">Edit Panel</Item>
      </Menu>
    </MenuTrigger>
  );
}
```

## Testing

The Menu features an overlay that transitions in and out of the page as it is opened and closed. Depending on
your configuration, this overlay may render as a tray or a dropdown and may feature long press interactions on the
trigger itself. Please see the following sections in the testing docs for more information on how to handle these
behaviors in your test suite.

[Timers](./testing.html#timers)

[Desktop vs Mobile](./testing.html#desktop-vs-mobile)

[Long press](./testing.html#simulating-user-long-press)

Please also refer to [React Spectrum's test suite](https://github.com/adobe/react-spectrum/blob/main/packages/%40react-spectrum/menu/test/MenuTrigger.test.js) if you find that the above
isn't sufficient when resolving issues in your own test cases.

### Test utils <VersionBadge version="beta" style={{marginLeft: 4, verticalAlign: 'bottom'}} />

`@react-spectrum/test-utils` offers common menu interaction utilities which you may find helpful when writing tests. See [here](./testing.html#react-spectrum-test-utils) for more information on how to setup these utilities
in your tests. Below is the full definition of the menu tester and a sample of how you could use it in your test suite.

```ts
// Menu.test.ts
import {render} from '@testing-library/react';
import {theme} from '@react-spectrum/theme-default';
import {User} from '@react-spectrum/test-utils';

let testUtilUser = new User({interactionType: 'mouse'});
// Other setup, be sure to check out the suggested mocks mentioned above in https://react-spectrum.adobe.com/react-spectrum/MenuTrigger.html#testing

it('Menu can open its submenu via keyboard', async function () {
  // Render your test component/app and initialize the menu tester
  let {getByTestId} = render(
    <Provider theme={defaultTheme}>
      <MenuTrigger>
        <Button data-testid="test-menutrigger">Menu trigger</Button>
        ...
      </MenuTrigger>
    </Provider>
  );
  let menuTester = testUtilUser.createTester('Menu', {root: getByTestId('test-menutrigger'), interactionType: 'keyboard'});

  await menuTester.open();
  expect(menuTester.menu).toBeInTheDocument();
  let submenuTriggers = menuTester.submenuTriggers;
  expect(submenuTriggers).toHaveLength(1);

  let submenuTester = await menuTester.openSubmenu({submenuTrigger: 'Share…'});
  expect(submenuTester.menu).toBeInTheDocument();

  await submenuTester.selectOption({option: submenuTester.options()[0]});
  expect(submenuTester.menu).not.toBeInTheDocument();
  expect(menuTester.menu).not.toBeInTheDocument();
});
```

<ClassAPI links={menuUtil.links} class={menuUtil.exports.MenuTester} />
